/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.group;
import java.beans.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.HashSet;
import java.util.Collections;
import java.text.MessageFormat;
import org.openide.TopManager;
import org.openide.loaders.*;
import org.openide.filesystems.*;
import org.openide.nodes.*;
import org.openide.text.EditorSupport;
import org.openide.util.datatransfer.PasteType;
import org.openide.util.datatransfer.ExTransferable;
import org.openide.util.NbBundle;
import org.openide.util.HelpCtx;
import org.openide.util.actions.SystemAction;
import org.openide.util.RequestProcessor;
import org.openide.cookies.CompilerCookie;
/** Group shadow.
* DataObject representing group of dataobjects on filesystem
* It also defines rules for templating of group members
* property templateALL
* property templatePattern defines message format where
* {0} file name
* {1} name entered by user
* {2} posfix got from {0} by using part following last "__"
* e.g. for "hello__World" is postfix "World"
* for "helloWorld" is postfix ""
* {3} backward substitution result
*
* @author Martin Ryzl. Pk
*/
public class GroupShadow extends DataObject {
/** Constants. */
public static String GS_EXTENSION = "group"; // NOI18N
/** Children for Group Shadow. */
private GroupChildren children;
/** If true, GroupShadow will show targets for all links. */
private static boolean showLinks = true;
/** Name of the Show Links Property. */
public static final String PROP_SHOW_LINKS = "showlinks"; // NOI18N
/** Name of the Template All Property. */
public static final String PROP_TEMPLATE_ALL = "templateall"; // NOI18N
/** Name of the Template Pattern Property */
public static final String PROP_TEMPLATE_PATTERN = "templatepattern"; // NOI18N
/** Message format for template pattern */
public String templatePattern = null;
/** If true create also group shadow when templating */
private boolean templateAll = false;
/** Icon resource string for GroupShadow node */
static final String GS_ICON_BASE =
"/org/netbeans/modules/group/resources/groupShadow"; // NOI18N
/** Format for display name. */
private static MessageFormat groupformat;
/** Formats for target names (valid, invalid, invalid). */
private static MessageFormat vformat, vformat2, iformat, iformat2;
/** Anti-loop detection. */
private GroupShadow gsprocessed = null;
static final long serialVersionUID =-5086491126656157958L;
/** Constructor
*
*/
public GroupShadow(final FileObject fo, DataLoader dl)
throws DataObjectExistsException, IllegalArgumentException {
super(fo, dl);
}
/** Creates a node for GroupShadow and registers it for listening
*@return node */
protected Node createNodeDelegate() {
GroupShadowNode node = new GroupShadowNode(this, new GroupChildren());
addPropertyChangeListener(node);
return node;
}
/* Getter for delete action.
* @return true if the object can be deleted
*/
public boolean isDeleteAllowed () {
return !getPrimaryFile ().isReadOnly ();
}
/* Getter for copy action.
* @return true if the object can be copied
*/
public boolean isCopyAllowed () {
return true;
}
/* Getter for move action.
* @return true if the object can be moved
*/
public boolean isMoveAllowed () {
return !getPrimaryFile ().isReadOnly ();
}
/* Getter for rename action.
* @return true if the object can be renamed
*/
public boolean isRenameAllowed () {
return !getPrimaryFile ().isReadOnly ();
}
/* Handles copy of the data object.
* @param f target folder
* @return the new data object
* @exception IOException if an error occures
*/
protected DataObject handleCopy (DataFolder f) throws IOException {
return handleCopy(f, getName());
}
protected DataObject handleCopy (DataFolder f, String name) throws IOException {
String newname = FileUtil.findFreeFileName (f.getPrimaryFile (), name, GS_EXTENSION);
FileObject fo = FileUtil.copyFile (getPrimaryFile (), f.getPrimaryFile (), newname);
return new GroupShadow(fo, getLoader());
}
/* Deals with deleting of the object. Must be overriden in children.
* @exception IOException if an error occures
*/
protected void handleDelete () throws IOException {
FileLock lock = getPrimaryFile ().lock ();
try {
getPrimaryFile ().delete (lock);
} finally {
lock.releaseLock ();
}
}
/* Handles renaming of the object.
* Must be overriden in children.
*
* @param name name to rename the object to
* @return new primary file of the object
* @exception IOException if an error occures
*/
protected FileObject handleRename (String name) throws IOException {
FileLock lock = getPrimaryFile ().lock ();
try {
getPrimaryFile ().rename (lock, name, GS_EXTENSION);
} finally {
lock.releaseLock ();
}
return getPrimaryFile ();
}
/* Handles move of the object. Must be overriden in children.
*
* @param f target data folder
* @return new primary file of the object
* @exception IOException if an error occures
*/
protected FileObject handleMove (DataFolder f) throws IOException {
String name = FileUtil.findFreeFileName (f.getPrimaryFile (), getName (), GS_EXTENSION);
return FileUtil.moveFile (getPrimaryFile (), f.getPrimaryFile (), name);
}
/* Help context for this object.
* @return help context
*/
public org.openide.util.HelpCtx getHelpCtx () {
return new HelpCtx (GroupShadow.class);
}
/** Adds a {@link CompilerCookie compilation cookie}.
*/
public Node.Cookie getCookie (Class cookie) {
if (CompilerCookie.class.isAssignableFrom (cookie)) {
GroupShadowCompiler c = new GroupShadowCompiler (this, cookie);
return c;
}
return super.getCookie (cookie);
}
/** Reads whole file to the List.
*
* @param fo file object to be read
*
* @return List of java.lang.String
*/
public static List readLinks(FileObject fo) throws IOException {
String line;
List list = new ArrayList();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(fo.getInputStream()));
while ((line = br.readLine()) != null) {
list.add(line);
}
} catch (IOException ex) {
throw ex;
}
finally {
if (br != null) br.close();
}
return list;
}
/** Reads whole file to the List.
*
* @return List of java.lang.String
*/
public List readLinks() throws IOException {
return readLinks(getPrimaryFile());
}
/** Writes List as new content of file.
*
* @param list List of java.lang.String
*/
public static void writeLinks(List list, FileObject fo) throws IOException {
String line;
Iterator iterator = list.iterator();
BufferedWriter bw = null;
FileLock lock = null;
try {
lock = fo.lock();
bw = new BufferedWriter(new OutputStreamWriter(fo.getOutputStream(lock)));
while (iterator.hasNext()) {
line = (String) iterator.next();
bw.write(line); bw.newLine();
}
} catch (IOException ex) {
throw ex;
}
finally {
if (lock != null) lock.releaseLock();
if (bw != null) {
bw.close();
}
}
}
/** Writes List as new content of file.
*
* @param list List of java.lang.String
*/
protected void writeLinks(List list) throws IOException {
writeLinks(list, getPrimaryFile());
}
/** Get link name.
*
* @param fo file object
* @return file name
*/
public static String getLinkName(FileObject fo) {
return fo.getPackageNameExt('/', '.');
}
/** Get FileObject for given filename.
*
* @param filename filename
* @return FileObject
*/
private static FileObject findFileObject(String filename) {
return TopManager.getDefault().getRepository().findResource(filename);
}
/** Get DataObject for given filename.
* @param filename filename
* @return DataObject
*/
private static DataObject getDataObjectByName(String filename) throws DataObjectNotFoundException {
FileObject tempfo = findFileObject(filename);
return (tempfo != null) ? DataObject.find(tempfo): null;
}
/** Reads filenames from shadow and creates an array of DataObjects for them.
*
* @return array that can contain DataObjects or Strings with names of invalid
* links
*/
public Object[] getLinks() {
FileObject pf = getPrimaryFile(), parent = pf.getParent(), tempfo;
DataObject obj;
String line;
HashSet set = new HashSet();
List linearray;
try {
linearray = readLinks(pf);
Iterator it = linearray.iterator();
while (it.hasNext()) {
line = (String)it.next();
try {
if ((obj = getDataObjectByName(line)) != null) {
set.add(obj);
}
else set.add(new String(line));
} catch (DataObjectNotFoundException ex) {
// can be thrown when the link is not recognized by any data loader
// in this case I can't help so ignore it
}
}
} catch (IOException ex) {
// it can be ignored
}
// the array can contain DataObjects and Strings !
return set.toArray();
}
/** Replace the oldprefix by a newprefix in name.
*
* @param name name
* @param oldprefix old prefix
* @param newprefix new prefix
* @return new name
*/
public static String createName(String name, String oldprefix, String newprefix) {
if (name.startsWith(oldprefix)) {
return newprefix + name.substring(oldprefix.length());
}
return name;
}
/** Replaces all occurences of __.*__ by a given text
*
* @param name name with __.*__ substrings
* @param pattern replacement
* @return a string with __.*__ replaced
*/
private String replaceName0(String name, String pattern) {
StringBuffer sb = new StringBuffer(256);
int i = 0, j, k;
while ((j = name.indexOf("__", i)) != -1) { // NOI18N
// first occurence found
k = name.indexOf("__", j + 2); // NOI18N
if (k != -1) {
// second occurence found, copy start part and pattern
sb.append(name.substring(i, j));
sb.append(pattern);
i = k + 2;
} else {
break;
}
}
// copy the rest
sb.append(name.substring(i, name.length ()));
return sb.toString();
}
/**Replaces name according to namming pattern defined by templatePattern
* property or fails to replaceName0() if the property is null
*/
private String replaceName(String name, String pattern) {
String fmt = getTemplatePattern();
if(fmt==null){
return replaceName0(name,pattern);
}
// filter out all characters before "__" // NOI18N
String postfix = ""; // NOI18N
try{
int i = name.lastIndexOf("__"); // NOI18N
if(i>0){
postfix = name.substring(i+2);
}
}catch(IndexOutOfBoundsException ex){
//use default value
}
String subst = string3(name,pattern);
return MessageFormat.format(fmt,new String[]{name,pattern,postfix,subst});
}
/**backward substitution in name by x
*SE: it calls recursively itself until whole substituion done
* x must not contain substitution pattern !!!
*/
private String substitute(String name, String x){
StringBuffer sb = new StringBuffer(name);
int j = name.length();
int i = name.lastIndexOf("__",j); // NOI18N
j = i-1;
if(i>=0){
i = name.lastIndexOf("__",j); // NOI18N
if(i>=0){
sb.delete(i,j+3);
sb.insert(i,x);
return substitute(sb.toString(),x);
}
}
return name;
}
/** Substitution wrapper for special cases
*@returns String representing new name after substitution
*/
private String string3(String name, String pattern) {
String patch;
if(name.startsWith("__")){ // NOI18N
patch = name;
}else{
patch = "__" + name; // NOI18N
}
String s3 = substitute(patch,pattern);
if (s3.startsWith("__")){ // NOI18N
s3 = s3.substring(2);
}
return s3;
}
/** HandleCreateFromTemplate implementation for GroupShadow.
*
*/
protected DataObject handleCreateFromTemplate(DataFolder df, String name) throws IOException {
DataObject original;
String originalName;
// anti-loop detection
if (gsprocessed == null) {
gsprocessed = this;
} else {
return this;
}
try {
Object[] objs = getLinks();
ArrayList list = new ArrayList(objs.length);
DataObject first = null;
for(int i = 0; i < objs.length; i++) {
if (objs[i] instanceof DataObject) {
original = (DataObject)objs[i];
originalName = original.getName();
DataObject obj = original.createFromTemplate(df, replaceName(originalName, name));
// if obj == this, the loop was detected and nothing was created
if ((first == null) && (obj != this)) first = obj;
list.add(getLinkName(obj.getPrimaryFile()));
}
}
// create GroupShadow
if (templateAll || (list.size() == 0)) {
GroupShadow gs = new GroupShadow(df.getPrimaryFile().createData(name, GS_EXTENSION), getLoader());
gs.writeLinks(list);
return gs;
}
if (first == null) return this;
return first;
} catch (IOException th) {
throw th;
} catch (Error e) {
throw e;
} finally {
// it must be set to null !
gsprocessed = null;
}
}
/** Setter for showLinks
*
* @param show if true also show real packages and names of targets
*/
public void setShowLinks(boolean show) {
showLinks = show;
}
/** Setter for showLinks
*
*/
public boolean getShowLinks() {
return showLinks;
}
/**XSetter for template pattern
*/
public void setTemplatePattern(String templatePattern) throws IOException{
final FileObject fo = getPrimaryFile();
String old = getTemplatePattern();
fo.setAttribute(PROP_TEMPLATE_PATTERN, templatePattern);
if (old != templatePattern) {
firePropertyChange(PROP_TEMPLATE_PATTERN, old, templatePattern);
}
}
/**XGetter for template pattern
*/
public String getTemplatePattern(){
Object o = getPrimaryFile().getAttribute(GroupShadow.PROP_TEMPLATE_PATTERN);
if (o instanceof String) return (String)o;
else return null;
}
/** Getter for template all
*
*/
public boolean getTemplateAll() {
Object o = getPrimaryFile().getAttribute(GroupShadow.PROP_TEMPLATE_ALL);
if (o instanceof Boolean) return ((Boolean) o).booleanValue();
else return false;
}
/** Setter for template all
*
*/
public void setTemplateAll(boolean templateAll) throws IOException {
final FileObject fo = getPrimaryFile();
boolean oldtempl = getTemplateAll();
fo.setAttribute(PROP_TEMPLATE_ALL, (templateAll ? new Boolean(true) : null));
if (oldtempl != templateAll) {
firePropertyChange(PROP_TEMPLATE_ALL, new Boolean(oldtempl), new Boolean(templateAll));
}
}
/** Getter for resources */
static String getLocalizedString (String s) {
return NbBundle.getBundle (GroupShadow.class).getString (s);
}
/* ====================== inner class(es) ======================== */
/** Node for group shadow. */
public class GroupShadowNode extends DataNode implements PropertyChangeListener{
/** Create a folder node with some children.
*
* @param ch children to use for the node
*/
public GroupShadowNode (final DataObject dob, Children children) {
super (dob, children);
setIconBase(GS_ICON_BASE);
}
/** Getter for display name .
*
* @return display name
*/
public String getDisplayName() {
if (groupformat == null) {
groupformat = new MessageFormat(GroupShadow.getLocalizedString("FMT_groupShadowName")); // NOI18N
}
String display = groupformat.format(new Object[] {
getName(), "", getPrimaryFile().toString(), "" // NOI18N
});
try {
display = getDataObject ().getPrimaryFile ().getFileSystem ().getStatus ().
annotateName (display, getDataObject ().files ());
} catch (FileStateInvalidException e) {
// no fs, do nothing
}
return display;
// return super.getDisplayName() + GroupShadow.getLocalizedString("PROP_group"); // " (group)"; // NOI18N
}
/** Initializes sheet of properties.
*
* @return sheet
*/
protected Sheet createSheet() {
Sheet s = super.createSheet();
updateSheet(s);
return s;
}
/**Listens for dataobject templatePattern property change
*according to it updates Experl list visibility
*/
public void propertyChange(PropertyChangeEvent evt) {
if(evt.getPropertyName()==DataObject.PROP_TEMPLATE){
if(evt.getNewValue().equals(evt.getOldValue())) return;
updateSheet(getSheet());
}
}
/**Conditionally fills the set
*/
private void fillExpertSet(Sheet.Set set){
DataObject obj = getDataObject();
Node.Property p;
// put properties to set
try {
/*X
p = new PropertySupport.Reflection (obj, Boolean.TYPE, "getShowLinks", "setShowLinks");
p.setName(GroupShadow.PROP_SHOW_LINKS);
p.setDisplayName(GroupShadow.getLocalizedString("PROP_showlinks"));
p.setShortDescription(GroupShadow.getLocalizedString("HINT_showlinks"));
ss.put(p);
*/
if(getDataObject().isTemplate()){
p = new PropertySupport.Reflection (obj, Boolean.TYPE, "getTemplateAll", "setTemplateAll"); // NOI18N
p.setName(GroupShadow.PROP_TEMPLATE_ALL);
p.setDisplayName(GroupShadow.getLocalizedString("PROP_templateall")); // NOI18N
p.setShortDescription(GroupShadow.getLocalizedString("HINT_templateall")); // NOI18N
set.put(p);
p = new PropertySupport.Reflection (obj, String.class, "getTemplatePattern", "setTemplatePattern"); // NOI18N
p.setName(GroupShadow.PROP_TEMPLATE_PATTERN);
p.setDisplayName(GroupShadow.getLocalizedString("PROP_templatePattern")); // NOI18N
p.setShortDescription(GroupShadow.getLocalizedString("HINT_templatePattern")); // NOI18N
set.put(p);
}
} catch (Exception ex) {
throw new InternalError();
}
}
/**On property isTemplate change
*/
private void updateSheet(Sheet sheet){
if(getDataObject().isTemplate()){
Sheet.Set set = sheet.get(Sheet.EXPERT);
if (set==null){
set = Sheet.createExpertSet();
fillExpertSet(set);
sheet.put(set);
}else{
fillExpertSet(set);
}
}else{
sheet.remove(Sheet.EXPERT);
}
}
/** Augments the default behaviour to test for {@link NodeTransfer#nodeCutFlavor} and
* {@link NodeTransfer#nodeCopyFlavor}
* with the {@link DataObject}. If there is such a flavor then adds
* the cut and copy flavors. Also, if there is a copy flavor and the
* data object is a template, adds an instantiate flavor.
*
* @param t transferable to use
* @param s list of {@link PasteType}s
*/
protected void createPasteTypes (Transferable t, java.util.List s) {
super.createPasteTypes (t, s);
DataObject obj = null;
// try copy flavor
obj = (DataObject)NodeTransfer.cookie (
t, NodeTransfer.CLIPBOARD_COPY | NodeTransfer.CLIPBOARD_CUT, DataObject.class
);
if (obj != null) {
if (obj.isCopyAllowed ()) {
// copy and cut
s.add (new Paste ("PT_copy", obj, false)); // NOI18N
}
}
}
}
/** Paste types for data objects. */
private class Paste extends PasteType {
private String resName;
private DataObject obj;
private boolean clearClipboard;
/**
* @param resName resource name for the name
* @param obj object to work with
* @param clear true if we should clear clipboard
*/
public Paste (String resName, DataObject obj, boolean clear) {
this.resName = resName;
this.obj = obj;
this.clearClipboard = clear;
}
/** The name is obtained from the bundle.
* @return the name
*/
public String getName () {
return getLocalizedString (resName);
}
/** Paste.
*/
public final Transferable paste () throws IOException {
handle (obj);
// clear clipboard or preserve content
return clearClipboard ? ExTransferable.EMPTY : null;
}
/** Handles the right action
* @param obj the data object to operate on
*/
public void handle (DataObject obj2) throws IOException {
List list = readLinks();
String name = getLinkName(obj2.getPrimaryFile());
if (list.indexOf(name) == -1) list.add(name);
writeLinks(list);
}
}
/** Children for group shadow. */
private class GroupChildren extends Children.Keys {
public GroupChildren() {
getPrimaryFile().addFileChangeListener(new FileChangeAdapter() {
public void fileChanged(FileEvent fe) {
update();
}
});
}
protected void addNotify() {
setKeys(Collections.EMPTY_SET);
RequestProcessor.postRequest(new Runnable() {
public void run() {
update();
}
});
}
protected void removeNotify() {
setKeys(Collections.EMPTY_SET);
}
void update() {
setKeys(getLinks());
}
protected Node[] createNodes(Object key) {
Node nodes[] = new Node[1];
if (key instanceof DataObject) {
nodes[0] = (Node) new GroupFilterNode(((DataObject)key).getNodeDelegate());
} else {
nodes[0] = (Node) new ErrorNode((String)key);
}
return nodes;
}
}
/** FilterNode representing one link. */
private class GroupFilterNode extends FilterNode {
public GroupFilterNode(Node original) {
super(original);
}
public void destroy() throws IOException {
DataObject obj = (DataObject)this.getCookie(DataObject.class), tempobj;
String name;
boolean modified = false;
if (obj != null) {
List list = readLinks();
Iterator it = list.iterator();
while (it.hasNext()) {
name = (String)it.next();
tempobj = getDataObjectByName(name);
if ((tempobj != null) && (tempobj.equals(obj))) {
it.remove(); modified = true;
}
}
if (modified) writeLinks(list);
}
}
public String getDisplayName() {
DataObject obj = (DataObject) this.getCookie(DataObject.class);
FileObject primary = obj.getPrimaryFile();
String name = primary.toString();
int index = name.lastIndexOf('/');
if (index > -1) name = name.substring(0, index + 1); else name = ""; // NOI18N
Object[] objs = new Object[] { obj.getName(), primary.toString(), name };
if (showLinks) {
if (vformat == null) {
vformat = new MessageFormat(GroupShadow.getLocalizedString("FMT_validTargetName")); // NOI18N
}
return vformat.format(objs);
} else {
if (vformat2 == null) {
vformat2 = new MessageFormat(GroupShadow.getLocalizedString("FMT_validTargetName2")); // NOI18N
}
return vformat2.format(objs);
}
}
}
/** Node representing an invalid link
*
*/
private class ErrorNode extends AbstractNode {
String name;
public ErrorNode(String name) {
super(Children.LEAF);
systemActions = new SystemAction[] {
SystemAction.get(org.openide.actions.DeleteAction.class),
null,
SystemAction.get(org.openide.actions.ToolsAction.class),
SystemAction.get(org.openide.actions.PropertiesAction.class)
};
this.name = name;
if (name != null) {
if (iformat == null) {
iformat = new MessageFormat(GroupShadow.getLocalizedString("FMT_invalidTargetName")); // NOI18N
}
DataObject obj = (DataObject) this.getCookie(DataObject.class);
setDisplayName(iformat.format(new Object[] { "", name })); // NOI18N
} else {
if (iformat2 == null) {
iformat2 = new MessageFormat(GroupShadow.getLocalizedString("FMT_invalidTargetName2")); // NOI18N
}
setDisplayName(iformat2.format(new Object[] { "", name })); // NOI18N
}
}
public ErrorNode() {
this(null);
}
public void destroy() throws IOException {
String name;
boolean modified = false;
List list = readLinks();
Iterator it = list.iterator();
while (it.hasNext()) {
name = (String)it.next();
if (name.equals(this.name)) {
it.remove(); modified = true;
}
if (modified) writeLinks(list);
}
}
public boolean canDestroy() {
return true;
}
}
}
/*
* Log
* 8 Gandalf 1.7 1/18/00 Jesse Glick Filesystem display name
* annotation.
* 7 Gandalf 1.6 1/14/00 Ian Formanek NOI18N
* 6 Gandalf 1.5 1/11/00 Martin Ryzl update for jdk1.3
* compilation
* 5 Gandalf 1.4 11/27/99 Patrik Knakal
* 4 Gandalf 1.3 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 3 Gandalf 1.2 9/16/99 Petr Kuzel Name patterns added
* 2 Gandalf 1.1 8/17/99 Martin Ryzl LoaderBeanInfo added,
* some bug corrected and some bananas around ..
* 1 Gandalf 1.0 7/29/99 Jaroslav Tulach
* $
*/